<template>
  <div
      :style="Object.assign(style, lineStyleObject)"
      :class="[{
      [classNameActive]: enabled,
      [classNameDragging]: dragging,
      [classNameResizing]: resizing,
      [classNameDraggable]: draggable,
      [classNameResizable]: resizable
    }, className]"
      @mousedown="elementMouseDown"
      @touchstart="elementTouchDown"
  >
    <div
        v-for="handle in actualHandles"
        :key="handle"
        :class="[classNameHandle, classNameHandle + '-' + handle]"
        :style="{display: enabled ? 'block' : 'none'}"
        @click.stop
        @mousedown.stop.prevent="handleDown(handle, $event)"
        @touchstart.stop.prevent="handleTouchDown(handle, $event)"
    >
      <slot :name="handle"></slot>
    </div>

    <div class="item-subline-wrapper" v-if="assistInfoEnabled">
      <div :class="groupItem ? 'xy_label group-item': 'xy_label'" :style="`font-size: ${12 * 1/scale}px;`"><i class="x">{{left}}</i>, <i class="y">{{top}}</i></div>
      <div :class="groupItem ? 'item_subline_h group-item': 'item_subline_h'"></div>
      <div :class="groupItem ? 'item_subline_v group-item': 'item_subline_v'"></div>
    </div>

    <div class="drag-align-wrapper" v-if="dragReferCollection.length > 0">
      <div class="drag-align-line-vertical" :style="`height: ${medianVerticalLineHeight}px; left: 50%; top: ${medianVerticalLineTop}px;`"></div>
      <div class="drag-align-line-horizontal" :style="`width: ${medianHorizontalLineWidth}px; left: ${medianHorizontalLineLeft}px; top: 50%;`"></div>
      <div class="drag-align-line-horizontal" :style="`width: ${topHorizontalLineWidth}px; left: ${topHorizontalLineLeft}px; top: -1px;`"></div>
      <div class="drag-align-line-horizontal" :style="`width: ${bottomHorizontalLineWidth}px; left: ${bottomHorizontalLineLeft}px; bottom: -1px;`"></div>
      <div class="drag-align-line-vertical" :style="`height: ${leftVerticalLineHeight}px; left: -1px; top: ${leftVerticalLineTop}px;`"></div>
      <div class="drag-align-line-vertical" :style="`height: ${rightVerticalLineHeight}px; right: -1px; top: ${rightVerticalLineTop}px;`"></div>
    </div>
    <slot></slot>
  </div>
</template>

<script>
import _ from 'lodash'

import { matchesSelectorToParentElements, getComputedSize, addEvent, removeEvent } from './utils/dom'
import { computeWidth, computeHeight, restrictToBounds, snapToGrid } from './utils/fns'

import { createHelpers } from 'vuex-map-fields';

const { mapFields } = createHelpers({
  getterType: 'designer/getField',
  mutationType: 'designer/updateField',
});

const events = {
  mouse: {
    start: 'mousedown',
    move: 'mousemove',
    stop: 'mouseup'
  },
  touch: {
    start: 'touchstart',
    move: 'touchmove',
    stop: 'touchend'
  }
}

const userSelectNone = {
  userSelect: 'none',
  MozUserSelect: 'none',
  WebkitUserSelect: 'none',
  MsUserSelect: 'none'
}

const userSelectAuto = {
  userSelect: 'auto',
  MozUserSelect: 'auto',
  WebkitUserSelect: 'auto',
  MsUserSelect: 'auto'
}

let eventsFor = events.mouse

export default {
  replace: true,
  name: 'vue-draggable-resizable',
  props: {
    className: {
      type: String,
      default: 'vdr'
    },
    classNameDraggable: {
      type: String,
      default: 'draggable'
    },
    classNameResizable: {
      type: String,
      default: 'resizable'
    },
    classNameDragging: {
      type: String,
      default: 'dragging'
    },
    classNameResizing: {
      type: String,
      default: 'resizing'
    },
    classNameActive: {
      type: String,
      default: 'active'
    },
    classNameHandle: {
      type: String,
      default: 'handle'
    },
    disableUserSelect: {
      type: Boolean,
      default: true
    },
    enableNativeDrag: {
      type: Boolean,
      default: false
    },
    preventDeactivation: {
      type: Boolean,
      default: false
    },
    active: {
      type: Boolean,
      default: false
    },
    draggable: {
      type: Boolean,
      default: true
    },
    resizable: {
      type: Boolean,
      default: true
    },
    lockAspectRatio: {
      type: Boolean,
      default: false
    },
    w: {
      type: [Number, String],
      default: 200,
      validator: (val) => {
        if (typeof val === 'number') {
          return val > 0
        }

        return val === 'auto'
      }
    },
    h: {
      type: [Number, String],
      default: 200,
      validator: (val) => {
        if (typeof val === 'number') {
          return val > 0
        }

        return val === 'auto'
      }
    },
    minWidth: {
      type: Number,
      default: 0,
      validator: (val) => val >= 0
    },
    minHeight: {
      type: Number,
      default: 0,
      validator: (val) => val >= 0
    },
    maxWidth: {
      type: Number,
      default: null,
      validator: (val) => val >= 0
    },
    maxHeight: {
      type: Number,
      default: null,
      validator: (val) => val >= 0
    },
    x: {
      type: Number,
      default: 0
    },
    y: {
      type: Number,
      default: 0
    },
    z: {
      type: [String, Number],
      default: 'auto',
      validator: (val) => (typeof val === 'string' ? val === 'auto' : val >= 0)
    },
    handles: {
      type: Array,
      default: () => ['tl', 'tm', 'tr', 'mr', 'br', 'bm', 'bl', 'ml'],
      validator: (val) => {
        const s = new Set(['tl', 'tm', 'tr', 'mr', 'br', 'bm', 'bl', 'ml'])

        return new Set(val.filter(h => s.has(h))).size === val.length
      }
    },
    dragHandle: {
      type: String,
      default: null
    },
    dragCancel: {
      type: String,
      default: null
    },
    axis: {
      type: String,
      default: 'both',
      validator: (val) => ['x', 'y', 'both'].includes(val)
    },
    grid: {
      type: Array,
      default: () => [1, 1]
    },
    parent: {
      type: Boolean,
      default: false
    },
    scale: {
      type: [Number, Array],
      default: 1,
      validator: (val) => {
        if (typeof val === 'number') {
          return val > 0
        }

        return val.length === 2 && val[0] > 0 && val[1] > 0
      }
    },
    onDragStart: {
      type: Function,
      default: () => true
    },
    onDrag: {
      type: Function,
      default: () => true
    },
    onResizeStart: {
      type: Function,
      default: () => true
    },
    onResize: {
      type: Function,
      default: () => true
    },

    layoutItem: {
      type: Object,
      default: () => {return null}
    },
    groupItem: {
      type: Object,
      default: () => {return null}
    }
  },
  data: function () {
    return {
      left: this.x,
      top: this.y,
      right: null,
      bottom: null,

      width: null,
      height: null,

      widthTouched: false,
      heightTouched: false,

      aspectFactor: null,

      parentWidth: null,
      parentHeight: null,

      minW: this.minWidth,
      minH: this.minHeight,

      maxW: this.maxWidth,
      maxH: this.maxHeight,

      handle: null,
      enabled: this.active,
      resizing: false,
      dragging: false,
      dragEnable: false,
      resizeEnable: false,
      zIndex: this.z,

      dragReferCollection: [], // 拖拽参照集合，集合项：{width: 0, height: 0, left: 0, top: 0}
      medianVerticalLineHeight: 0,
      medianVerticalLineTop: 0,
      medianHorizontalLineWidth: 0,
      medianHorizontalLineLeft: 0,
      topHorizontalLineWidth: 0,
      topHorizontalLineLeft: 0,
      bottomHorizontalLineWidth: 0,
      bottomHorizontalLineLeft: 0,
      leftVerticalLineHeight: 0,
      leftVerticalLineTop: 0,
      rightVerticalLineHeight: 0,
      rightVerticalLineTop: 0
    }
  },
  created: function () {
    // eslint-disable-next-line
    if (this.maxWidth && this.minWidth > this.maxWidth) console.warn('[Vdr warn]: Invalid prop: minWidth cannot be greater than maxWidth')
    // eslint-disable-next-line
    if (this.maxWidth && this.minHeight > this.maxHeight) console.warn('[Vdr warn]: Invalid prop: minHeight cannot be greater than maxHeight')

    this.resetBoundsAndMouseState()
  },
  mounted: function () {
    if (!this.enableNativeDrag) {
      this.$el.ondragstart = () => false
    }

    const [parentWidth, parentHeight] = this.getParentSize()

    this.parentWidth = parentWidth
    this.parentHeight = parentHeight

    const [width, height] = getComputedSize(this.$el)

    this.aspectFactor = (this.w !== 'auto' ? this.w : width) / (this.h !== 'auto' ? this.h : height)

    this.width = this.w !== 'auto' ? this.w : width
    this.height = this.h !== 'auto' ? this.h : height

    this.right = this.parentWidth - this.width - this.left
    this.bottom = this.parentHeight - this.height - this.top

    if (this.active) {
      this.$emit('activated')
    }

    addEvent(document.documentElement, 'mousedown', this.deselect)
    addEvent(document.documentElement, 'touchend touchcancel', this.deselect)

    addEvent(window, 'resize', this.checkParentSize)
  },
  beforeDestroy: function () {
    removeEvent(document.documentElement, 'mousedown', this.deselect)
    removeEvent(document.documentElement, 'touchstart', this.handleUp)
    removeEvent(document.documentElement, 'mousemove', this.move)
    removeEvent(document.documentElement, 'touchmove', this.move)
    removeEvent(document.documentElement, 'mouseup', this.handleUp)
    removeEvent(document.documentElement, 'touchend touchcancel', this.deselect)

    removeEvent(window, 'resize', this.checkParentSize)
  },
  methods: {
    // 初始化拖拽对齐参照集合（算法可以有待优化）
    initDragReferCollection () {
      if (this.pageMetadata.layout.layoutConfigData.dragPixel == 1) { // 判断页面的拖拽单位像素等于1的时候才会初始化拖拽对齐参照集合
        this.dragReferCollection = []; // 重置dragReferCollection
        let addDragReferCollectionItem = (id, configData) => {
          if (configData.display == 'block') {
            this.dragReferCollection.push({
              id: id,
              width: configData.width,
              height: configData.height,
              left: configData.left,
              top: configData.top
            })
          }
        }
        let excludeLayoutItems = []
        for (let i=0; i<this.layoutGroup.length; i++) {
          excludeLayoutItems = excludeLayoutItems.concat(this.layoutGroup[i].layoutItemIds)
        }
        let tmpLayoutItems = _.filter(this.layoutItems, function (o) {
          return excludeLayoutItems.indexOf(o.id) == -1
        }) // 排除已被成组的布局块
        let allGroupItemAndLayoutItem = _.concat(tmpLayoutItems, this.layoutGroup)
        for (let i=0; i<allGroupItemAndLayoutItem.length; i++) {
          if (allGroupItemAndLayoutItem[i].layoutItemConfigData) {
            addDragReferCollectionItem(allGroupItemAndLayoutItem[i].id, allGroupItemAndLayoutItem[i].layoutItemConfigData)
          }else if (allGroupItemAndLayoutItem[i].groupItemConfigData) {
            addDragReferCollectionItem(allGroupItemAndLayoutItem[i].id, allGroupItemAndLayoutItem[i].groupItemConfigData)
          }
        }
        if (this.layoutItem) {
          this.dragReferCollection = _.filter(this.dragReferCollection, (o) => {
            return o.id != this.layoutItem.id
          })
        }else if (this.groupItem) {
          this.dragReferCollection = _.filter(this.dragReferCollection, (o) => {
            return o.id != this.groupItem.id
          })
        }

        // 当存在单独显示的图层时，就排除不在单独显示图层中的其他参照项
        if (this.$store.state.designer.alongShowTargetIdList.length > 0) {
          this.dragReferCollection = _.filter(this.dragReferCollection, (o) => {
            return this.$store.state.designer.alongShowTargetIdList.indexOf(o.id) > -1
          })
        }
        // console.log(this.dragReferCollection)
      }
    },
    resetBoundsAndMouseState () {
      this.mouseClickPosition = { mouseX: 0, mouseY: 0, x: 0, y: 0, w: 0, h: 0 }

      this.bounds = {
        minLeft: null,
        maxLeft: null,
        minRight: null,
        maxRight: null,
        minTop: null,
        maxTop: null,
        minBottom: null,
        maxBottom: null
      }
    },
    checkParentSize () {
      if (this.parent) {
        const [newParentWidth, newParentHeight] = this.getParentSize()

        this.parentWidth = newParentWidth
        this.parentHeight = newParentHeight
        this.right = this.parentWidth - this.width - this.left
        this.bottom = this.parentHeight - this.height - this.top
      }
    },
    getParentSize () {
      if (this.parent) {
        const style = window.getComputedStyle(this.$el.parentNode, null)

        return [
          parseInt(style.getPropertyValue('width'), 10),
          parseInt(style.getPropertyValue('height'), 10)
        ]
      }

      return [null, null]
    },
    elementTouchDown (e) {
      eventsFor = events.touch

      this.elementDown(e)
    },
    elementMouseDown (e) {
      eventsFor = events.mouse

      this.elementDown(e)
    },
    elementDown (e) {
      if (e instanceof MouseEvent && e.which !== 1) {
        return
      }

      const target = e.target || e.srcElement

      /*if (!this.enabled) {
        this.enabled = true

        this.$emit('activated')
        this.$emit('update:active', true)
      }else {
        if (this.$el.contains(target)) {
          if (this.onDragStart(e) === false) {
            return
          }

          if (
              (this.dragHandle && !matchesSelectorToParentElements(target, this.dragHandle, this.$el)) ||
              (this.dragCancel && matchesSelectorToParentElements(target, this.dragCancel, this.$el))
          ) {
            this.dragging = false

            return
          }

          if (this.draggable) {
            this.dragEnable = true
          }

          this.mouseClickPosition.mouseX = e.touches ? e.touches[0].pageX : e.pageX
          this.mouseClickPosition.mouseY = e.touches ? e.touches[0].pageY : e.pageY

          this.mouseClickPosition.left = this.left
          this.mouseClickPosition.right = this.right
          this.mouseClickPosition.top = this.top
          this.mouseClickPosition.bottom = this.bottom

          if (this.parent) {
            this.bounds = this.calcDragLimits()
          }

          addEvent(document.documentElement, eventsFor.move, this.move)
          addEvent(document.documentElement, eventsFor.stop, this.handleUp)
        }
      }*/

      if (this.$el.contains(target)) {
        if (this.onDragStart(e) === false) {
          return
        }

        if (
            (this.dragHandle && !matchesSelectorToParentElements(target, this.dragHandle, this.$el)) ||
            (this.dragCancel && matchesSelectorToParentElements(target, this.dragCancel, this.$el))
        ) {
          this.dragging = false

          return
        }

        if (!this.enabled) {
          this.enabled = true

          this.initDragReferCollection()

          this.$emit('activated')
          this.$emit('update:active', true)
        }

        if (this.draggable) {
          this.dragEnable = true
        }

        this.mouseClickPosition.mouseX = e.touches ? e.touches[0].pageX : e.pageX
        this.mouseClickPosition.mouseY = e.touches ? e.touches[0].pageY : e.pageY

        this.mouseClickPosition.left = this.left
        this.mouseClickPosition.right = this.right
        this.mouseClickPosition.top = this.top
        this.mouseClickPosition.bottom = this.bottom

        if (this.parent) {
          this.bounds = this.calcDragLimits()
        }

        addEvent(document.documentElement, eventsFor.move, this.move)
        addEvent(document.documentElement, eventsFor.stop, this.handleUp)
      }
    },
    calcDragLimits () {
      return {
        minLeft: this.left % this.grid[0],
        maxLeft: Math.floor((this.parentWidth - this.width - this.left) / this.grid[0]) * this.grid[0] + this.left,
        minRight: this.right % this.grid[0],
        maxRight: Math.floor((this.parentWidth - this.width - this.right) / this.grid[0]) * this.grid[0] + this.right,
        minTop: this.top % this.grid[1],
        maxTop: Math.floor((this.parentHeight - this.height - this.top) / this.grid[1]) * this.grid[1] + this.top,
        minBottom: this.bottom % this.grid[1],
        maxBottom: Math.floor((this.parentHeight - this.height - this.bottom) / this.grid[1]) * this.grid[1] + this.bottom
      }
    },
    deselect (e) {
      const target = e.target || e.srcElement
      const regex = new RegExp(this.className + '-([trmbl]{2})', '')

      if (!this.$el.contains(target) && !regex.test(target.className)) {
        if (this.enabled && !this.preventDeactivation) {
          this.enabled = false

          // 重置拖拽参照集合
          this.dragReferCollection = []

          this.$emit('deactivated')
          this.$emit('update:active', false)
        }

        removeEvent(document.documentElement, eventsFor.move, this.handleResize)
      }

      this.resetBoundsAndMouseState()
    },
    handleTouchDown (handle, e) {
      eventsFor = events.touch

      this.handleDown(handle, e)
    },
    handleDown (handle, e) {
      if (e instanceof MouseEvent && e.which !== 1) {
        return
      }

      if (this.onResizeStart(handle, e) === false) {
        return
      }

      if (e.stopPropagation) e.stopPropagation()

      if (this.lockAspectRatio && !handle.includes('m')) {
        this.handle = 'm' + handle.substring(1)
      } else {
        this.handle = handle
      }

      this.resizeEnable = true

      this.mouseClickPosition.mouseX = e.touches ? e.touches[0].pageX : e.pageX
      this.mouseClickPosition.mouseY = e.touches ? e.touches[0].pageY : e.pageY
      this.mouseClickPosition.left = this.left
      this.mouseClickPosition.right = this.right
      this.mouseClickPosition.top = this.top
      this.mouseClickPosition.bottom = this.bottom

      this.bounds = this.calcResizeLimits()

      addEvent(document.documentElement, eventsFor.move, this.handleResize)
      addEvent(document.documentElement, eventsFor.stop, this.handleUp)
    },
    calcResizeLimits () {
      let minW = this.minW
      let minH = this.minH
      let maxW = this.maxW
      let maxH = this.maxH

      const aspectFactor = this.aspectFactor
      const [gridX, gridY] = this.grid
      const width = this.width
      const height = this.height
      const left = this.left
      const top = this.top
      const right = this.right
      const bottom = this.bottom

      if (this.lockAspectRatio) {
        if (minW / minH > aspectFactor) {
          minH = minW / aspectFactor
        } else {
          minW = aspectFactor * minH
        }

        if (maxW && maxH) {
          maxW = Math.min(maxW, aspectFactor * maxH)
          maxH = Math.min(maxH, maxW / aspectFactor)
        } else if (maxW) {
          maxH = maxW / aspectFactor
        } else if (maxH) {
          maxW = aspectFactor * maxH
        }
      }

      maxW = maxW - (maxW % gridX)
      maxH = maxH - (maxH % gridY)

      const limits = {
        minLeft: null,
        maxLeft: null,
        minTop: null,
        maxTop: null,
        minRight: null,
        maxRight: null,
        minBottom: null,
        maxBottom: null
      }

      if (this.parent) {
        limits.minLeft = left % gridX
        limits.maxLeft = left + Math.floor((width - minW) / gridX) * gridX
        limits.minTop = top % gridY
        limits.maxTop = top + Math.floor((height - minH) / gridY) * gridY
        limits.minRight = right % gridX
        limits.maxRight = right + Math.floor((width - minW) / gridX) * gridX
        limits.minBottom = bottom % gridY
        limits.maxBottom = bottom + Math.floor((height - minH) / gridY) * gridY

        if (maxW) {
          limits.minLeft = Math.max(limits.minLeft, this.parentWidth - right - maxW)
          limits.minRight = Math.max(limits.minRight, this.parentWidth - left - maxW)
        }

        if (maxH) {
          limits.minTop = Math.max(limits.minTop, this.parentHeight - bottom - maxH)
          limits.minBottom = Math.max(limits.minBottom, this.parentHeight - top - maxH)
        }

        if (this.lockAspectRatio) {
          limits.minLeft = Math.max(limits.minLeft, left - top * aspectFactor)
          limits.minTop = Math.max(limits.minTop, top - left / aspectFactor)
          limits.minRight = Math.max(limits.minRight, right - bottom * aspectFactor)
          limits.minBottom = Math.max(limits.minBottom, bottom - right / aspectFactor)
        }
      } else {
        limits.minLeft = null
        limits.maxLeft = left + Math.floor((width - minW) / gridX) * gridX
        limits.minTop = null
        limits.maxTop = top + Math.floor((height - minH) / gridY) * gridY
        limits.minRight = null
        limits.maxRight = right + Math.floor((width - minW) / gridX) * gridX
        limits.minBottom = null
        limits.maxBottom = bottom + Math.floor((height - minH) / gridY) * gridY

        if (maxW) {
          limits.minLeft = -(right + maxW)
          limits.minRight = -(left + maxW)
        }

        if (maxH) {
          limits.minTop = -(bottom + maxH)
          limits.minBottom = -(top + maxH)
        }

        if (this.lockAspectRatio && (maxW && maxH)) {
          limits.minLeft = Math.min(limits.minLeft, -(right + maxW))
          limits.minTop = Math.min(limits.minTop, -(maxH + bottom))
          limits.minRight = Math.min(limits.minRight, -left - maxW)
          limits.minBottom = Math.min(limits.minBottom, -top - maxH)
        }
      }

      return limits
    },
    move (e) {
      if (this.resizing) {
        this.handleResize(e)
      } else if (this.dragEnable) {
        this.handleDrag(e)
      }
    },
    dragAlign () { // 拖拽对齐
      let medianVerticalLineConfigData,
          medianHorizontalLineConfigData,
          topHorizontalLineConfigData,
          bottomHorizontalLineConfigData,
          leftVerticalLineConfigData,
          rightVerticalLineConfigData = null;

      for (let i=0; i<this.dragReferCollection.length; i++) {
        if (Math.abs((this.left + Math.floor(this.w / 2)) - (Math.floor(this.dragReferCollection[i].width / 2) + this.dragReferCollection[i].left)) < this.dragAlignMagnetThreshold) {
          medianVerticalLineConfigData = this.dragReferCollection[i]
        } // 拖拽到目标组件水平中间位置
        if (Math.abs((this.top + Math.floor(this.h / 2)) - (Math.floor(this.dragReferCollection[i].height / 2) + this.dragReferCollection[i].top)) < this.dragAlignMagnetThreshold) {
          medianHorizontalLineConfigData = this.dragReferCollection[i]
        } // 拖拽到目标组件垂直中间位置
        if (Math.abs(this.top - this.dragReferCollection[i].top) < this.dragAlignMagnetThreshold) {
          topHorizontalLineConfigData = this.dragReferCollection[i]
        }
        if (Math.abs((this.top + this.h) - (this.dragReferCollection[i].top + this.dragReferCollection[i].height)) < this.dragAlignMagnetThreshold) {
          bottomHorizontalLineConfigData = this.dragReferCollection[i]
        }
        if (Math.abs(this.left - this.dragReferCollection[i].left) < this.dragAlignMagnetThreshold) {
          leftVerticalLineConfigData = this.dragReferCollection[i]
        }
        if (Math.abs((this.left + this.w) - (this.dragReferCollection[i].left + this.dragReferCollection[i].width)) < this.dragAlignMagnetThreshold) {
          rightVerticalLineConfigData = this.dragReferCollection[i]
        }
      }

      if (medianVerticalLineConfigData) {
        this.left = (Math.floor(medianVerticalLineConfigData.width / 2) + medianVerticalLineConfigData.left) - Math.floor(this.w / 2); // 磁吸
        if (this.h <= medianVerticalLineConfigData.height) { // 如果当前拖拽的组件高度小于等于目标组件的高度
          if (this.top >= medianVerticalLineConfigData.top) { // 当前组件的top大于等于目标组件的top，也就是在目标组件的下方
            if (this.h + (this.top - medianVerticalLineConfigData.top) <= medianVerticalLineConfigData.height) {
              this.medianVerticalLineHeight = medianVerticalLineConfigData.height
            }else {
              this.medianVerticalLineHeight =
                  medianVerticalLineConfigData.height + ((this.top + this.h) - (medianVerticalLineConfigData.top + medianVerticalLineConfigData.height))
            }
            this.medianVerticalLineTop = medianVerticalLineConfigData.top - this.top
          }else {
            this.medianVerticalLineHeight = medianVerticalLineConfigData.height + (medianVerticalLineConfigData.top - this.top)
            this.medianVerticalLineTop = 0
          }
        }else {
          if (this.top >= medianVerticalLineConfigData.top) { // 当前组件的top大于等于目标组件的top，也就是在目标组件的下方
            this.medianVerticalLineHeight =
                medianVerticalLineConfigData.height + ((this.top + this.h) - (medianVerticalLineConfigData.top + medianVerticalLineConfigData.height))
            this.medianVerticalLineTop = medianVerticalLineConfigData.top - this.top
          }else {
            if (medianVerticalLineConfigData.top - this.top <= (this.h - medianVerticalLineConfigData.height)) {
              this.medianVerticalLineHeight = this.h
            }else {
              this.medianVerticalLineHeight = medianVerticalLineConfigData.height + (medianVerticalLineConfigData.top - this.top)
            }
            this.medianVerticalLineTop = 0
          }
        }
      }else {
        this.medianVerticalLineHeight = 0
      }

      if (medianHorizontalLineConfigData) {
        this.top = (Math.floor(medianHorizontalLineConfigData.height / 2) + medianHorizontalLineConfigData.top) - Math.floor(this.h / 2); // 磁吸
        if (this.w <= medianHorizontalLineConfigData.width) { // 如果当前拖拽的组件宽度小于等于目标组件的宽度
          if (this.left >= medianHorizontalLineConfigData.left) { // 当前组件的left大于等于目标组件的left，也就是在目标组件的右边
            if (this.w + (this.left - medianHorizontalLineConfigData.left) <= medianHorizontalLineConfigData.width) {
              this.medianHorizontalLineWidth = medianHorizontalLineConfigData.width
            }else {
              this.medianHorizontalLineWidth =
                  medianHorizontalLineConfigData.width + ((this.left + this.w) - (medianHorizontalLineConfigData.left + medianHorizontalLineConfigData.width))
            }
            this.medianHorizontalLineLeft = medianHorizontalLineConfigData.left - this.left
          }else {
            this.medianHorizontalLineWidth = medianHorizontalLineConfigData.width + (medianHorizontalLineConfigData.left - this.left)
            this.medianHorizontalLineLeft = 0
          }
        }else {
          if (this.left >= medianHorizontalLineConfigData.left) { // 当前组件的left大于等于目标组件的left，也就是在目标组件的右边
            this.medianHorizontalLineWidth =
                medianHorizontalLineConfigData.width + ((this.left + this.w) - (medianHorizontalLineConfigData.left + medianHorizontalLineConfigData.width))
            this.medianHorizontalLineLeft = medianHorizontalLineConfigData.left - this.left
          }else {
            if (medianHorizontalLineConfigData.left - this.left <= (this.w - medianHorizontalLineConfigData.width)) {
              this.medianHorizontalLineWidth = this.w
            }else {
              this.medianHorizontalLineWidth = medianHorizontalLineConfigData.width + (medianHorizontalLineConfigData.left - this.left)
            }
            this.medianHorizontalLineLeft = 0
          }
        }
      }else {
        this.medianHorizontalLineWidth = 0
      }

      if (topHorizontalLineConfigData) {
        this.top = topHorizontalLineConfigData.top
        if (this.left > topHorizontalLineConfigData.left) { // 当前拖拽组件在目标组件的右边
          this.topHorizontalLineWidth = (this.left + this.w) - topHorizontalLineConfigData.left
          this.topHorizontalLineLeft = topHorizontalLineConfigData.left - this.left
        }else {
          this.topHorizontalLineWidth = (topHorizontalLineConfigData.left + topHorizontalLineConfigData.width) - this.left
          this.topHorizontalLineLeft = 0
        }
      }else {
        this.topHorizontalLineWidth = 0
      }

      if (bottomHorizontalLineConfigData) {
        this.top = (bottomHorizontalLineConfigData.top + bottomHorizontalLineConfigData.height) - this.h
        if (this.left > bottomHorizontalLineConfigData.left) { // 当前拖拽组件在目标组件的右边
          this.bottomHorizontalLineWidth = (this.left + this.w) - bottomHorizontalLineConfigData.left
          this.bottomHorizontalLineLeft = bottomHorizontalLineConfigData.left - this.left
        }else {
          this.bottomHorizontalLineWidth = (bottomHorizontalLineConfigData.left + bottomHorizontalLineConfigData.width) - this.left
          this.bottomHorizontalLineLeft = 0
        }
      }else {
        this.bottomHorizontalLineWidth = 0
      }

      if (leftVerticalLineConfigData) {
        this.left = leftVerticalLineConfigData.left
        if (this.top >= leftVerticalLineConfigData.top) { // 当前组件的top大于等于目标组件的top，也就是在目标组件的下方
          this.leftVerticalLineHeight = leftVerticalLineConfigData.height + ((this.top + this.h) - (leftVerticalLineConfigData.top + leftVerticalLineConfigData.height))
          this.leftVerticalLineTop = leftVerticalLineConfigData.top - this.top
        }else {
          this.leftVerticalLineHeight = leftVerticalLineConfigData.height + (leftVerticalLineConfigData.top - this.top)
          this.leftVerticalLineTop = 0
        }
      }else {
        this.leftVerticalLineHeight = 0
      }

      if (rightVerticalLineConfigData) {
        this.left = (rightVerticalLineConfigData.left + rightVerticalLineConfigData.width) - this.w
        if (this.top >= rightVerticalLineConfigData.top) { // 当前组件的top大于等于目标组件的top，也就是在目标组件的下方
          this.rightVerticalLineHeight = (this.top + this.h) - rightVerticalLineConfigData.top
          this.rightVerticalLineTop = rightVerticalLineConfigData.top - this.top
        }else {
          this.rightVerticalLineHeight = (rightVerticalLineConfigData.top + rightVerticalLineConfigData.height) - this.top
          this.rightVerticalLineTop = 0
        }
      }else {
        this.rightVerticalLineHeight = 0
      }
    },
    handleDrag (e) {
      const axis = this.axis
      const grid = this.grid
      const bounds = this.bounds
      const mouseClickPosition = this.mouseClickPosition

      const tmpDeltaX = axis && axis !== 'y' ? mouseClickPosition.mouseX - (e.touches ? e.touches[0].pageX : e.pageX) : 0
      const tmpDeltaY = axis && axis !== 'x' ? mouseClickPosition.mouseY - (e.touches ? e.touches[0].pageY : e.pageY) : 0

      const [deltaX, deltaY] = snapToGrid(grid, tmpDeltaX, tmpDeltaY, this.scale)

      const left = restrictToBounds(mouseClickPosition.left - deltaX, bounds.minLeft, bounds.maxLeft)
      const top = restrictToBounds(mouseClickPosition.top - deltaY, bounds.minTop, bounds.maxTop)

      if (this.onDrag(left, top) === false) {
        return
      }

      const right = restrictToBounds(mouseClickPosition.right + deltaX, bounds.minRight, bounds.maxRight)
      const bottom = restrictToBounds(mouseClickPosition.bottom + deltaY, bounds.minBottom, bounds.maxBottom)

      this.left = left
      this.top = top
      this.right = right
      this.bottom = bottom

      if (Math.abs(left) < this.rulerGuidesMagnetThreshold) { // 靠近页面最左边
        this.left = 0
      }
      if (Math.abs(this.left - (this.pageMetadata.layout.layoutConfigData.width - this.w)) < this.rulerGuidesMagnetThreshold) { // 靠近页面最右边
        this.left = this.pageMetadata.layout.layoutConfigData.width - this.w
      }
      if (Math.abs(top) < this.rulerGuidesMagnetThreshold) { // 靠近页面最上边
        this.top = 0
      }
      if (Math.abs(this.top - (this.pageMetadata.layout.layoutConfigData.height - this.h)) < this.rulerGuidesMagnetThreshold) { // 靠近页面最下边
        this.top = this.pageMetadata.layout.layoutConfigData.height - this.h
      }
      // 标尺参考线磁吸
      if (this.pageLinesData[this.pageMetadata.id]) {
        this.pageLinesData[this.pageMetadata.id].h.forEach(hItem => {
          if (Math.abs(hItem - left) < this.rulerGuidesMagnetThreshold) {
            this.left = hItem
          }
          if (Math.abs(hItem - (left + this.w)) < this.rulerGuidesMagnetThreshold) {
            this.left = hItem - this.w
          }
        })
        this.pageLinesData[this.pageMetadata.id].v.forEach(vItem => {
          if (Math.abs(vItem - top) < this.rulerGuidesMagnetThreshold) {
            this.top = vItem
          }
          if (Math.abs(vItem - (top + this.h)) < this.rulerGuidesMagnetThreshold) {
            this.top = vItem - this.h
          }
        })
      }
      // 标尺参考线磁吸 end

      this.dragAlign()

      this.$emit('dragging', this.left, this.top)
      this.dragging = true
    },
    moveHorizontally (val) {
      // should calculate with scale 1.
      const [deltaX, _] = snapToGrid(this.grid, val, this.top, 1)

      const left = restrictToBounds(deltaX, this.bounds.minLeft, this.bounds.maxLeft)

      this.left = left
      this.right = this.parentWidth - this.width - left
    },
    moveVertically (val) {
      // should calculate with scale 1.
      const [_, deltaY] = snapToGrid(this.grid, this.left, val, 1)

      const top = restrictToBounds(deltaY, this.bounds.minTop, this.bounds.maxTop)

      this.top = top
      this.bottom = this.parentHeight - this.height - top
    },
    resizeAlign () {
      let topHorizontalLineConfigData,
          bottomHorizontalLineConfigData,
          leftVerticalLineConfigData,
          rightVerticalLineConfigData = null;

      for (let i=0; i<this.dragReferCollection.length; i++) {
        if (this.top == this.dragReferCollection[i].top) {
          topHorizontalLineConfigData = this.dragReferCollection[i]
        }
        if ((this.top + this.height) == (this.dragReferCollection[i].top + this.dragReferCollection[i].height)) {
          bottomHorizontalLineConfigData = this.dragReferCollection[i]
        }
        if (this.left == this.dragReferCollection[i].left) {
          leftVerticalLineConfigData = this.dragReferCollection[i]
        }
        if ((this.left + this.width) == (this.dragReferCollection[i].left + this.dragReferCollection[i].width)) {
          rightVerticalLineConfigData = this.dragReferCollection[i]
        }
      }

      if (topHorizontalLineConfigData) {
        if (this.handle.includes('t')) {
          if (this.left > topHorizontalLineConfigData.left) { // 当前拖拽组件在目标组件的右边
            this.topHorizontalLineWidth = (this.left + this.width) - topHorizontalLineConfigData.left
            this.topHorizontalLineLeft = topHorizontalLineConfigData.left - this.left
          }else {
            this.topHorizontalLineWidth = (topHorizontalLineConfigData.left + topHorizontalLineConfigData.width) - this.left
            this.topHorizontalLineLeft = 0
          }
        }
      }else {
        this.topHorizontalLineWidth = 0
      }

      if (bottomHorizontalLineConfigData) {
        if (this.handle.includes('b')) {
          if (this.left > bottomHorizontalLineConfigData.left) { // 当前拖拽组件在目标组件的右边
            this.bottomHorizontalLineWidth = (this.left + this.width) - bottomHorizontalLineConfigData.left
            this.bottomHorizontalLineLeft = bottomHorizontalLineConfigData.left - this.left
          }else {
            this.bottomHorizontalLineWidth = (bottomHorizontalLineConfigData.left + bottomHorizontalLineConfigData.width) - this.left
            this.bottomHorizontalLineLeft = 0
          }
        }
      }else {
        this.bottomHorizontalLineWidth = 0
      }

      if (leftVerticalLineConfigData) {
        if (this.handle.includes('l')) {
          if (this.top >= leftVerticalLineConfigData.top) { // 当前组件的top大于等于目标组件的top，也就是在目标组件的下方
            this.leftVerticalLineHeight = (this.top + this.height) - (leftVerticalLineConfigData.top)
            this.leftVerticalLineTop = leftVerticalLineConfigData.top - this.top
          }else {
            this.leftVerticalLineHeight = (leftVerticalLineConfigData.top + leftVerticalLineConfigData.height) - this.top
            this.leftVerticalLineTop = 0
          }
        }
      }else {
        this.leftVerticalLineHeight = 0
      }

      if (rightVerticalLineConfigData) {
        if (this.handle.includes('r')) {
          if (this.top >= rightVerticalLineConfigData.top) { // 当前组件的top大于等于目标组件的top，也就是在目标组件的下方
            this.rightVerticalLineHeight = (this.top + this.height) - (rightVerticalLineConfigData.top)
            this.rightVerticalLineTop = rightVerticalLineConfigData.top - this.top
          }else {
            this.rightVerticalLineHeight = (rightVerticalLineConfigData.top + rightVerticalLineConfigData.height) - this.top
            this.rightVerticalLineTop = 0
          }
        }
      }else {
        this.rightVerticalLineHeight = 0
      }
    },
    handleResize (e) {
      let left = this.left
      let top = this.top
      let right = this.right
      let bottom = this.bottom

      const mouseClickPosition = this.mouseClickPosition
      const lockAspectRatio = this.lockAspectRatio
      const aspectFactor = this.aspectFactor

      const tmpDeltaX = mouseClickPosition.mouseX - (e.touches ? e.touches[0].pageX : e.pageX)
      const tmpDeltaY = mouseClickPosition.mouseY - (e.touches ? e.touches[0].pageY : e.pageY)

      if (!this.widthTouched && tmpDeltaX) {
        this.widthTouched = true
      }

      if (!this.heightTouched && tmpDeltaY) {
        this.heightTouched = true
      }

      const [deltaX, deltaY] = snapToGrid(this.grid, tmpDeltaX, tmpDeltaY, this.scale)

      if (this.handle.includes('b')) {
        bottom = restrictToBounds(
            mouseClickPosition.bottom + deltaY,
            this.bounds.minBottom,
            this.bounds.maxBottom
        )

        if (this.lockAspectRatio && this.resizingOnY) {
          right = this.right - (this.bottom - bottom) * aspectFactor
        }
      } else if (this.handle.includes('t')) {
        top = restrictToBounds(
            mouseClickPosition.top - deltaY,
            this.bounds.minTop,
            this.bounds.maxTop
        )

        if (this.lockAspectRatio && this.resizingOnY) {
          left = this.left - (this.top - top) * aspectFactor
        }
      }

      if (this.handle.includes('r')) {
        right = restrictToBounds(
            mouseClickPosition.right + deltaX,
            this.bounds.minRight,
            this.bounds.maxRight
        )

        if (this.lockAspectRatio && this.resizingOnX) {
          bottom = this.bottom - (this.right - right) / aspectFactor
        }
      } else if (this.handle.includes('l')) {
        left = restrictToBounds(
            mouseClickPosition.left - deltaX,
            this.bounds.minLeft,
            this.bounds.maxLeft
        )

        if (this.lockAspectRatio && this.resizingOnX) {
          top = this.top - (this.left - left) / aspectFactor
        }
      }

      const width = computeWidth(this.parentWidth, left, right)
      const height = computeHeight(this.parentHeight, top, bottom)

      if (this.onResize(this.handle, left, top, width, height) === false) {
        return
      }

      this.left = left
      this.top = top
      this.right = right
      this.bottom = bottom
      this.width = width
      this.height = height

      this.resizeAlign()

      this.$emit('resizing', this.left, this.top, this.width, this.height)
      this.resizing = true
    },
    changeWidth (val) {
      // should calculate with scale 1.
      const [newWidth, _] = snapToGrid(this.grid, val, 0, 1)

      let right = restrictToBounds(
          (this.parentWidth - newWidth - this.left),
          this.bounds.minRight,
          this.bounds.maxRight
      )
      let bottom = this.bottom

      if (this.lockAspectRatio) {
        bottom = this.bottom - (this.right - right) / this.aspectFactor
      }

      const width = computeWidth(this.parentWidth, this.left, right)
      const height = computeHeight(this.parentHeight, this.top, bottom)

      this.right = right
      this.bottom = bottom
      this.width = width
      this.height = height
    },
    changeHeight (val) {
      // should calculate with scale 1.
      const [_, newHeight] = snapToGrid(this.grid, 0, val, 1)

      let bottom = restrictToBounds(
          (this.parentHeight - newHeight - this.top),
          this.bounds.minBottom,
          this.bounds.maxBottom
      )
      let right = this.right

      if (this.lockAspectRatio) {
        right = this.right - (this.bottom - bottom) * this.aspectFactor
      }

      const width = computeWidth(this.parentWidth, this.left, right)
      const height = computeHeight(this.parentHeight, this.top, bottom)

      this.right = right
      this.bottom = bottom
      this.width = width
      this.height = height
    },
    handleUp (e) {
      this.handle = null

      this.resetBoundsAndMouseState()

      this.dragEnable = false
      this.resizeEnable = false

      if (this.resizing) { // 尺寸调整结束时运行
        this.resizing = false
        setTimeout(() => {
          this.initDragReferCollection()
        }, 100)
        this.$emit('resizestop', this.left, this.top, this.width, this.height)
      }

      if (this.dragging) { // 拖拽位置结束时运行
        this.dragging = false
        setTimeout(() => {
          this.initDragReferCollection()
        }, 100)
        this.$emit('dragstop', this.left, this.top)
      }

      // 重置拖拽对齐相关参考线的尺寸为0
      this.medianVerticalLineHeight = 0
      this.medianHorizontalLineWidth = 0
      this.topHorizontalLineWidth = 0
      this.bottomHorizontalLineWidth = 0
      this.leftVerticalLineHeight = 0
      this.rightVerticalLineHeight = 0

      removeEvent(document.documentElement, eventsFor.move, this.handleResize)
    }
  },
  computed: {
    ...mapFields({
      pageMetadata: 'pageMetadata',
      layoutItems: 'pageMetadata.layout.layoutItems',
      layoutGroup: 'pageMetadata.layout.layoutGroup',
      currentSelectGroupItemId: 'currentSelectGroupItemId',
      currentSelectGroupItemIds: 'currentSelectGroupItemIds',
      currentSelectLayoutItemId: 'currentSelectLayoutItemId',
      currentSelectLayoutItemIds: 'currentSelectLayoutItemIds',
      pageLinesData: 'pageLinesData',
      rulerGuidesMagnetThreshold: 'rulerGuidesMagnetThreshold',
      dragAlignMagnetThreshold: 'dragAlignMagnetThreshold'
    }),
    style () {
      return {
        transform: `translate(${this.left}px, ${this.top}px)`,
        width: this.computedWidth,
        height: this.computedHeight,
        zIndex: this.zIndex,
        ...(this.dragging && this.disableUserSelect ? userSelectNone : userSelectAuto)
      }
    },
    actualHandles () {
      if (!this.resizable) return []

      return this.handles
    },
    computedWidth () {
      if (this.w === 'auto') {
        if (!this.widthTouched) {
          return 'auto'
        }
      }

      return this.width + 'px'
    },
    computedHeight () {
      if (this.h === 'auto') {
        if (!this.heightTouched) {
          return 'auto'
        }
      }

      return this.height + 'px'
    },
    resizingOnX () {
      return (Boolean(this.handle) && (this.handle.includes('l') || this.handle.includes('r')))
    },
    resizingOnY () {
      return (Boolean(this.handle) && (this.handle.includes('t') || this.handle.includes('b')))
    },
    isCornerHandle () {
      return (Boolean(this.handle) && ['tl', 'tr', 'br', 'bl'].includes(this.handle))
    },

    assistInfoEnabled () {
      if (this.groupItem) {
        return this.groupItem.id == this.currentSelectGroupItemId || this.currentSelectGroupItemIds.indexOf(this.groupItem.id) > -1
      }
      if (this.layoutItem) {
        return this.layoutItem.id == this.currentSelectLayoutItemId || this.currentSelectLayoutItemIds.indexOf(this.layoutItem.id) > -1
      }
      return true
    },
    lineStyleObject () {
      return {
        '--line-width': (1/this.scale) + 'px',
        '--drag-align-line-width': (1.2/this.scale) + 'px'
      }
    }
  },
  watch: {
    active (val) {
      this.enabled = val

      if (val) {
        this.$emit('activated')
      } else {
        this.$emit('deactivated')
      }
    },
    z (val) {
      if (val >= 0 || val === 'auto') {
        this.zIndex = val
      }
    },
    x (val) {
      if (this.resizing || this.dragging) {
        return
      }

      if (this.parent) {
        this.bounds = this.calcDragLimits()
      }

      this.moveHorizontally(val)
    },
    y (val) {
      if (this.resizing || this.dragging) {
        return
      }

      if (this.parent) {
        this.bounds = this.calcDragLimits()
      }

      this.moveVertically(val)
    },
    lockAspectRatio (val) {
      if (val) {
        this.aspectFactor = this.width / this.height
      } else {
        this.aspectFactor = undefined
      }
    },
    minWidth (val) {
      if (val > 0 && val <= this.width) {
        this.minW = val
      }
    },
    minHeight (val) {
      if (val > 0 && val <= this.height) {
        this.minH = val
      }
    },
    maxWidth (val) {
      this.maxW = val
    },
    maxHeight (val) {
      this.maxH = val
    },
    w (val) {
      if (this.resizing || this.dragging) {
        return
      }

      if (this.parent) {
        this.bounds = this.calcResizeLimits()
      }

      this.changeWidth(val)
    },
    h (val) {
      if (this.resizing || this.dragging) {
        return
      }

      if (this.parent) {
        this.bounds = this.calcResizeLimits()
      }

      this.changeHeight(val)
    }
  }
}
</script>

